import sys
import numpy as np
from PyQt5.QtWidgets import QApplication, QMainWindow, QVBoxLayout, QWidget, QPushButton, QTextEdit
from PyQt5.QtChart import QChart, QChartView, QBoxPlotSeries, QBoxSet
from PyQt5.QtCore import Qt
from PyQt5.QtGui import QColor, QPainter


class BoxPlotDataManager:
    """盒须图数据管理器"""
    
    def __init__(self):
        self.series = QBoxPlotSeries()
        self.box_sets = {}  # 数据集字典
        self.categories = []  # 类别列表
        self.setup_series()
    
    def setup_series(self):
        """配置盒须图系列基本属性"""
        # 设置系列名称
        self.series.setName("数据分布分析")
        
        # 设置盒须图属性
        self.series.setBoxWidth(0.5)  # 盒子宽度（相对值）
        
        # 连接信号
        self.series.clicked.connect(self.on_box_clicked)
        self.series.hovered.connect(self.on_box_hovered)
    
    def calculate_box_statistics(self, data: list) -> tuple:
        """
        计算盒须图的五个关键统计量
        返回: (最小值, 下四分位数, 中位数, 上四分位数, 最大值)
        """
        if not data:
            return (0, 0, 0, 0, 0), []
        
        data_sorted = sorted(data)
        n = len(data_sorted)
        
        # 计算中位数
        if n % 2 == 0:
            median = (data_sorted[n//2 - 1] + data_sorted[n//2]) / 2
        else:
            median = data_sorted[n//2]
        
        # 计算四分位数
        q1_index = n // 4
        q3_index = 3 * n // 4
        
        q1 = data_sorted[q1_index] if n >= 4 else data_sorted[0]
        q3 = data_sorted[q3_index] if n >= 4 else data_sorted[-1]
        
        # 计算四分位距和异常值边界
        iqr = q3 - q1
        lower_fence = q1 - 1.5 * iqr
        upper_fence = q3 + 1.5 * iqr
        
        # 计算须线端点（排除异常值）
        lower_whisker = min(x for x in data_sorted if x >= lower_fence)
        upper_whisker = max(x for x in data_sorted if x <= upper_fence)
        
        # 识别异常值
        outliers = [x for x in data_sorted if x < lower_fence or x > upper_fence]
        
        return (lower_whisker, q1, median, q3, upper_whisker), outliers
    
    def add_box_set(self, name: str, data: list = None, color: QColor = None):
        """添加盒须图数据集"""
        if data is None:
            data = []
        
        # 计算统计量
        statistics, outliers = self.calculate_box_statistics(data)
        lower_whisker, q1, median, q3, upper_whisker = statistics
        
        # 创建盒须图数据集
        box_set = QBoxSet(name)
        
        # 正确设置盒须图的值
        box_set.append([lower_whisker, q1, median, q3, upper_whisker])
        
        # 存储额外的统计信息
        box_set.statistics = {
            'lower_whisker': lower_whisker,
            'q1': q1,
            'median': median,
            'q3': q3,
            'upper_whisker': upper_whisker,
            'outliers': outliers
        }
        
        # 设置颜色
        if color is None:
            color = self.generate_color(len(self.box_sets))
        box_set.setBrush(color)
        
        # 存储数据集
        self.box_sets[name] = box_set
        self.series.append(box_set)
        
        return box_set
    
    def add_box_set_from_statistics(self, name: str, 
                                  lower_whisker: float, q1: float, median: float, 
                                  q3: float, upper_whisker: float,
                                  outliers: list = None, color: QColor = None):
        """直接从统计量添加盒须图数据集"""
        box_set = QBoxSet(name)
        
        # 正确设置盒须图的值
        box_set.append([lower_whisker, q1, median, q3, upper_whisker])
        
        # 存储异常值
        if outliers is None:
            outliers = []
        
        # 存储额外的统计信息
        box_set.statistics = {
            'lower_whisker': lower_whisker,
            'q1': q1,
            'median': median,
            'q3': q3,
            'upper_whisker': upper_whisker,
            'outliers': outliers
        }
        
        # 设置颜色
        if color is None:
            color = self.generate_color(len(self.box_sets))
        box_set.setBrush(color)
        
        # 存储数据集
        self.box_sets[name] = box_set
        self.series.append(box_set)
        
        return box_set
    
    def set_categories(self, categories: list):
        """设置类别列表"""
        self.categories = categories
    
    def generate_sample_data(self, count: int = 5):
        """生成样本数据"""
        self.clear_all_data()
        
        # 定义类别
        categories = ["数据集A", "数据集B", "数据集C", "数据集D", "数据集E"]
        self.set_categories(categories)
        
        # 为每个类别生成数据
        for i, category in enumerate(categories):
            # 生成不同分布的数据
            if i == 0:
                # 正态分布
                data = np.random.normal(50, 10, 100).tolist()
            elif i == 1:
                # 偏态分布
                data = np.random.gamma(2, 2, 100).tolist()
            elif i == 2:
                # 均匀分布
                data = np.random.uniform(30, 70, 100).tolist()
            elif i == 3:
                # 双峰分布
                data1 = np.random.normal(30, 5, 50).tolist()
                data2 = np.random.normal(60, 5, 50).tolist()
                data = data1 + data2
            else:
                # 包含异常值的分布
                data = np.random.normal(50, 8, 95).tolist()
                # 添加一些异常值
                data.extend([10, 15, 90, 95])
            
            self.add_box_set(category, data)
    
    def clear_all_data(self):
        """清空所有数据"""
        self.series.clear()
        self.box_sets.clear()
        self.categories.clear()
    
    def get_statistical_summary(self, box_set_name: str) -> dict:
        """获取数据集的统计摘要"""
        if box_set_name not in self.box_sets:
            return {}
        
        box_set = self.box_sets[box_set_name]
        
        # 使用存储的统计信息
        stats = box_set.statistics
        summary = {
            'min': stats['lower_whisker'],
            'q1': stats['q1'],
            'median': stats['median'],
            'q3': stats['q3'],
            'max': stats['upper_whisker'],
            'iqr': stats['q3'] - stats['q1'],
            'outlier_count': len(stats['outliers'])
        }
        
        return summary
    
    def detect_distribution_type(self, box_set_name: str) -> str:
        """检测数据分布类型"""
        summary = self.get_statistical_summary(box_set_name)
        if not summary:
            return "未知"
        
        median = summary['median']
        q1 = summary['q1']
        q3 = summary['q3']
        
        # 计算偏度（基于四分位数）
        left_spread = median - q1
        right_spread = q3 - median
        
        if abs(left_spread - right_spread) / ((left_spread + right_spread) / 2) < 0.1:
            return "对称分布"
        elif left_spread > right_spread:
            return "左偏分布"
        else:
            return "右偏分布"
    
    def generate_color(self, index: int) -> QColor:
        """生成颜色（基于索引）"""
        colors = [
            QColor(70, 130, 180, 150),   # 半透明钢蓝色
            QColor(220, 100, 100, 150),   # 半透明红色
            QColor(100, 180, 100, 150),   # 半透明绿色
            QColor(180, 180, 50, 150),    # 半透明黄色
            QColor(150, 100, 180, 150),   # 半透明紫色
        ]
        return colors[index % len(colors)]
    
    def on_box_clicked(self, box_set: QBoxSet):
        """盒须图点击事件处理"""
        summary = self.get_statistical_summary(box_set.label())
        print(f"盒须图被点击: {box_set.label()}")
        print(f"统计摘要: {summary}")
    
    def on_box_hovered(self, status: bool, box_set: QBoxSet):
        """盒须图悬停事件处理"""
        if status:
            summary = self.get_statistical_summary(box_set.label())
            print(f"鼠标悬停在: {box_set.label()}")
            print(f"中位数: {summary['median']:.2f}, IQR: {summary['iqr']:.2f}")


class MainWindow(QMainWindow):
    def __init__(self):
        super().__init__()
        self.setWindowTitle("盒须图数据可视化")
        self.setGeometry(100, 100, 1200, 800)
        
        # 创建数据管理器
        self.data_manager = BoxPlotDataManager()
        
        # 创建UI
        self.setup_ui()
        
        # 生成示例数据
        self.data_manager.generate_sample_data()
        
        # 创建图表
        self.create_chart()
    
    def setup_ui(self):
        """设置用户界面"""
        central_widget = QWidget()
        self.setCentralWidget(central_widget)
        
        layout = QVBoxLayout(central_widget)
        
        # 创建图表视图
        self.chart_view = QChartView()
        layout.addWidget(self.chart_view)
        
        # 创建按钮区域
        button_layout = QVBoxLayout()
        
        # 刷新数据按钮
        self.refresh_btn = QPushButton("刷新数据")
        self.refresh_btn.clicked.connect(self.refresh_data)
        button_layout.addWidget(self.refresh_btn)
        
        # 显示统计信息按钮
        self.stats_btn = QPushButton("显示统计信息")
        self.stats_btn.clicked.connect(self.show_statistics)
        button_layout.addWidget(self.stats_btn)
        
        layout.addLayout(button_layout)
        
        # 创建文本显示区域
        self.text_display = QTextEdit()
        self.text_display.setMaximumHeight(150)
        layout.addWidget(self.text_display)
    
    def create_chart(self):
        """创建图表"""
        chart = QChart()
        chart.setTitle("盒须图数据分布分析")
        chart.setAnimationOptions(QChart.SeriesAnimations)
        
        # 添加系列到图表
        chart.addSeries(self.data_manager.series)
        chart.createDefaultAxes()
        
        # 设置坐标轴
        axis_x = chart.axisX()
        axis_y = chart.axisY()
        
        if axis_x:
            axis_x.setTitleText("数据集")
        if axis_y:
            axis_y.setTitleText("数值")
        
        # 设置图表主题
        chart.setTheme(QChart.ChartThemeBlueCerulean)
        
        # 将图表设置到视图
        self.chart_view.setChart(chart)
        self.chart_view.setRenderHint(QPainter.Antialiasing)
    
    def refresh_data(self):
        """刷新数据"""
        self.data_manager.generate_sample_data()
        self.create_chart()
        self.text_display.append("数据已刷新")
    
    def show_statistics(self):
        """显示统计信息"""
        self.text_display.clear()
        self.text_display.append("=== 数据集统计信息 ===\n")
        
        for category in self.data_manager.categories:
            summary = self.data_manager.get_statistical_summary(category)
            dist_type = self.data_manager.detect_distribution_type(category)
            
            stats_text = f"""
{category}:
  最小值: {summary['min']:.2f}
  下四分位数(Q1): {summary['q1']:.2f}
  中位数: {summary['median']:.2f}
  上四分位数(Q3): {summary['q3']:.2f}
  最大值: {summary['max']:.2f}
  四分位距(IQR): {summary['iqr']:.2f}
  异常值数量: {summary['outlier_count']}
  分布类型: {dist_type}
            """
            self.text_display.append(stats_text)


def main():
    # 创建应用
    app = QApplication(sys.argv)
    
    # 创建主窗口
    window = MainWindow()
    window.show()
    
    # 运行应用
    sys.exit(app.exec_())


if __name__ == "__main__":
    main()